/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2018年4月26日
 * V4.0
 */
package com.jphenix.driver.dbshell;

import com.jphenix.standard.docs.ClassInfo;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Statement;
import java.util.List;
import java.util.Map;

/**
 * 底层数据处理事件容器
 * com.jphenix.driver.dbshell.EventVO
 * 
 * 2019-03-15 修改了初始化时抛异常的问题
 * 
 * 
 * @author MBG
 * 2018年4月26日
 */
@ClassInfo({"2019-03-15 13:36","底层数据处理事件容器"})
public class EventVO extends ClassLoader {
    
    private static Object kernel = null; //事件类实例
    
    /**
     * 设置事件类实例
     * @param evt 事件类实例
     * 2018年4月26日
     * @author MBG
     */
    @SuppressWarnings("rawtypes")
    public void setEvent(Object evt) {
        kernel = evt;
        
            /*
             * 比如运行在Tomcat环境中时：
             * 
             * 有的项目数据源由Tomcat加载，导致Driver类由Tomcat底层的AppClassLoader加载
             * 通常事件类实例都是写在脚本中，脚本是由其子类WebAppClassLoader加载，这导致了
             * 不是在同一个类加载器中构造的类实例，导致设置事件类实例时，由WebAppClassLoader
             * 重新构造了EventVO中的static的kernel类实例，而获取数据库连接时，调用的EventVO中
             * 的kernel仍然是null的，无法拦截得到
             * 
             * 所以采用以下机制：
             * 
             * 1. 先尝试用父类加载器加载EventVO类（及假设当前为WebAppClassLoader,尝试从AppClassLoader加载）
             * 2. 如果当前就是AppClassLoader，则直接等于EventVO.class
             * 3. 将事件类实例存放在父类加载器中的EventVO类中
             * 4. 如果是子类加载器加载的EventVO类需要用到事件类实例（即：kernel），也是从父类加载器中获取到
             *    EventVO（不同的类实例）然后获取到事件类实例
             * 
             */
        Class parent = null;
        try{
            parent = loadClass(EventVO.class.getName(),true);
        }catch(Exception e) {}
        try {
            if(parent==null) {
                parent = EventVO.class;
            }
            if(parent!=null) {
                //获取到变量声明对象
                Field field = parent.getDeclaredField("kernel");
                field.setAccessible(true); //开挂，外部访问private变量
                field.set(null,evt);       //设置值
            }
        }catch(Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取事件类实例
     * @return 事件类实例
     * 2018年4月27日
     * @author MBG
     */
    @SuppressWarnings("rawtypes")
    private Object getEvent() {
        if(kernel==null) {
            try {
                /*
                 * 原理同setEvent
                 * 如果当前类实例中事件类实例为空，则从父类加载器中获取这个EventVO类，
                 * 尝试获取父类加载器环境中EventVO类的kernel类实例
                 */
                Class parent = super.loadClass(EventVO.class.getName(),true);
                if(parent==null) {
                    return null;
                }
                Field field = parent.getDeclaredField("kernel");
                field.setAccessible(true);  //开挂，外部访问private变量
                kernel = field.get(null);   //获取值
            }catch(Exception e) {
                return null;
            }
        }
        return kernel;
    }
    
    /**
     * 在执行更新语句前调用该方法
     * @param source         数据源主键
     * @param sql            更新语句
     * @param parameterCount 提交参数值数量
     * @param parameterMap   提交参数值容器 key：参数索引  value：参数值对象
     * @param stat           处理对象 可能为 Statement,PreparedStatement,CallableStatement
     * 
     * 如果为PreparedStatement,CallableStatement，可以通过 getParameterMetaData()
     * 方法获取到提交数据的值
     */
    public Object beforeExecute(String source,String sql,int parameterCount,Map<Integer,Object> parameterMap, Statement stat) {
        if(getEvent()==null) {
            return null;
        }
        try {
            //通过反射方式，执行事件，因为事件类与当前类实例的环境可能不是同一个类加载器
            Method method = kernel.getClass().getDeclaredMethod("beforeExecute", String.class,String.class,int.class,Map.class,Statement.class);
            return method.invoke(kernel, source,sql,parameterCount,parameterMap,stat);
        }catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 批量执行的语句调用前的方法
     * @param source       数据源主键
     * @param batchSqlList 需要批量执行的语句序列（执行该方法后会清空该序列）
     * @param stat         处理对象
     */
    public Object beforeExecute(String source,List<String> batchSqlList, Statement stat) {
        if(getEvent()==null) {
            return null;
        }
        try {
          //通过反射方式，执行事件，因为事件类与当前类实例的环境可能不是同一个类加载器
            Method method = kernel.getClass().getDeclaredMethod("beforeExecute", String.class,List.class,Statement.class);
            return method.invoke(kernel, source,batchSqlList,stat);
        }catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * 在执行语句后调用该方法
     * @param source      数据源主键
     * @param paraObj    在执行beforeExecute返回的对象（可以用来保存之前记录的数据）
     * @param status      执行结果 true成功 false无效
     * @param updateCount 更新记录数量
     * 2018年5月2日
     * @author MBG
     */
    public void afterExecute(String source,Object paraObj,boolean status,int updateCount) {
        if(getEvent()==null) {
            return;
        }
        try {
            //通过反射方式，执行事件，因为事件类与当前类实例的环境可能不是同一个类加载器
            Method method = kernel.getClass().getDeclaredMethod("afterExecute", String.class,Object.class,boolean.class,int.class);
            method.invoke(kernel, source,paraObj,status,updateCount);
        }catch(Exception e) {
            e.printStackTrace();
        }  
    }
}
